/**
 * Created by Jacky.gao on 2016/6/28.
 */
import Connection from './Connection.js';
import SelectTool from './tools/SelectTool.js';
import ConnectionTool from './tools/ConnectionTool.js';
import * as MsgBox from './MsgBox.js';
export default class Node {
    constructor(config = {}) {
        this.fromConnections = [];
        this.toConnections = [];
    }
    _initConfigs(config) {
        this.in = config.in;
        if (!this.in && this.in !== 0) {
            this.in = -1;//-1 is unlimited
        }
        this.out = config.out;
        if (!this.out && this.out !== 0) {
            this.out = -1;
        }
        this.single = config.single;//Whether the current instance can have more then one
    }
    getSvgIcon() {
        throw 'Unsupport this method.';
    }

    validate() {
        return null;
    }

    initFromJson(json) {
        let { x, y, name, connections } = json;
        let width = json.width || json.w, height = json.height || json.h;
        x = parseInt(x), y = parseInt(y), width = parseInt(width), height = parseInt(height);
        this.changeSize(width, height);
        this.move(x, y, width, height);
        this.name = name;
        this.text.attr('text', name);
        if (json.uuid) {
            this.uuid = json.uuid;
        }
        this.connections = json.connections;
        this.fromConnectionsJson = json.fromConnections;
        this.toConnectionsJson = json.toConnections;
    }

    _buildConnections() {
        if (this.fromConnectionsJson) {
            for (let json of this.fromConnectionsJson) {
                let toNodeUUID = json.toUUID, fromNodeUUID = json.fromUUID;
                const toNode = this.context.getNodeByUUID(toNodeUUID), fromNode = this.context.getNodeByUUID(fromNodeUUID);
                const newConnection = new Connection(fromNode, { endX: 0, endY: 0 });
                newConnection.fromJSON(json);
                newConnection.endPath(toNode);
            }
        }
        if (this.toConnectionsJson) {
            for (let json of this.toConnectionsJson) {
                let toNodeUUID = json.toUUID, fromNodeUUID = json.fromUUID;
                const toNode = this.context.getNodeByUUID(toNodeUUID), fromNode = this.context.getNodeByUUID(fromNodeUUID);
                const newConnection = new Connection(fromNode, { endX: 0, endY: 0 });
                newConnection.fromJSON(json);
                newConnection.endPath(toNode);
            }
        }
        if (this.connections) {
            for (let json of this.connections) {
                let fromNode = this, to = json.to, toNode = null;
                for (let figure of this.context.allFigures) {
                    if (figure instanceof Connection) {
                        continue;
                    }
                    if (figure.name === to) {
                        toNode = figure;
                        break;
                    }
                }
                if (!toNode) {
                    MsgBox.alert(`连线的目标节点${to}不存在<br>Node ${to} not exist`);
                    return;
                }
                const newConnection = new Connection(fromNode, { endX: 0, endY: 0 });
                newConnection.endPath(toNode);
                newConnection.fromJSON(json);
            }
        }
    }

    toJSON() {
        return this.nodeToJSON();
    }

    nodeToJSON() {
        let json = {
            x: this.rect.attr('x'),
            y: this.rect.attr('y'),
            w: this.rect.attr('width'),
            h: this.rect.attr('height'),
            name: this.name,
            uuid: this.uuid
        };
        const fromConnections = [], toConnections = [];
        this.fromConnections.forEach((conn, index) => {
            fromConnections.push(conn.toJSON());
        });
        this.toConnections.forEach((conn, index) => {
            toConnections.push(conn.toJSON());
        });
        json.fromConnections = fromConnections;
        json.toConnections = toConnections;
        return json;
    }

    _createFigure(context, pos, name) {
        if (this.single) {
            let exist = false;
            for (let figure of context.allFigures) {
                if (figure instanceof this.constructor) {
                    exist = true;
                    break;
                }
            }
            if (exist) {
                MsgBox.alert('当前节点只允许创建一个<br>Current Node only create one.');
                return false;
            }
        }
        this.uuid = context.nextUUID();
        this.context = context;
        this.paper = context.paper;
        const w = 40, h = 70;
        pos = { x: pos.x - w / 2, y: pos.y - h / 2 + 15 };
        this.rect = this.paper.rect(pos.x, pos.y, w, h);
        this.rect.attr({ 'fill': '#fff', 'stroke': '#fff', 'stroke-dasharray': '--' });
        this.context.allFigures.push(this);
        this.svgIconPath = this.getSvgIcon();
        this.icon = this.paper.image(this.svgIconPath, pos.x, pos.y, 40, 40);
        this.name = name;
        const textX = pos.x + w / 2, textY = pos.y + h - 16;
        this.text = this.paper.text(textX, textY, this.name);
        this.text.attr({ 'font-size': '16pt' });
        this.text.mousedown(function (e) {
            e.preventDefault();
        });
        this._initFigure();
        return true;
    }

    moveTo(centerX, centerY) {
        const w = this.rect.attr('width'), h = this.rect.attr('height');
        let x, y;
        if (centerX === -1) {
            x = this.rect.attr('x');
            y = centerY - h / 2;
        }
        if (centerY === -1) {
            y = this.rect.attr('y');
            x = centerX - w / 2;
        }
        this.move(x, y, w, h);
    }

    move(x, y, w, h) {
        this.rect.attr({ x, y });
        const textX = x + w / 2, textY = y + h - 16;
        this.text.attr({ x: textX, y: textY });
        const iconX = x - w / 2, iconY = y - h / 2 + 20, iconW = this.icon.attr('width'), iconH = this.icon.attr('height');
        this.icon.attr({ x: iconX + iconW / 2, y: iconY + iconH / 2 });
        this._resetConnections();
        if (window._setDirty) {
            window._setDirty();
        }
    }

    changeSize(w, h) {
        const x = this.rect.attr('x'), y = this.rect.attr('y');
        this.rect.attr({ width: w, height: h });
        const textX = x + w / 2, textY = y + h - 16;
        this.text.attr({ x: textX, y: textY });
        const iconW = w, iconH = h - 30;
        this.icon.attr({ width: iconW, height: iconH });
        this._resetConnections();
        if (window._setDirty) {
            window._setDirty();
        }
    }

    _initFigure() {
        var context = this.context;
        var _this = this;
        var mouseOver = function (e, mouseX, mouseY) {
            if (this.dragging === true) {
                return;
            }
            const currentTool = context.currentTool;
            if (!currentTool) {
                return;
            }
            if (currentTool instanceof ConnectionTool) {
                this.attr('cursor', 'pointer');
            }
            if (!(currentTool instanceof SelectTool)) {
                return;
            }
            var container = context.container;
            var relativeX = mouseX - container.offset().left - this.attr('x');
            var relativeY = mouseY - container.offset().top - this.attr('y');
            var shapeWidth = this.attr('width');
            var shapeHeight = this.attr('height');
            var resizeBorder = 5;
            // Change cursor...20230309因903项目要求，不允许调整节点图标大小，所以变更以下代码
            /**
            if (relativeX < resizeBorder && relativeY < resizeBorder) {
                this.attr('cursor', 'nw-resize');//在此属性中，光标指示框的边将向上和向左移动。
            } else if (relativeX > shapeWidth - resizeBorder && relativeY < resizeBorder) {
                this.attr('cursor', 'ne-resize');//在此属性中，光标指示框的边将向上和向右移动。
            } else if (relativeX > shapeWidth - resizeBorder && relativeY > shapeHeight - resizeBorder) {
                this.attr('cursor', 'se-resize');//在此属性中，光标指示框的边将被上下移动。
            } else if (relativeX < resizeBorder && relativeY > shapeHeight - resizeBorder) {
                this.attr('cursor', 'sw-resize');//在此属性中，光标指示框的边要上下移动。
            } else {
                this.attr('cursor', 'move');
            }
            */
            this.attr('cursor', 'move');
        };

        var mouseDown = function (e) {
            if (this.dragging) {
                return;
            }
            let currentTool = context.currentTool;
            if (!currentTool) {
                return;
            }
            if (!(currentTool instanceof ConnectionTool)) {
                if (!(currentTool instanceof SelectTool)) {
                    context.currentTool = context.selectTool;
                    currentTool = context.currentTool;
                    context.flowDesigner.nodeToolbar.children('label').removeClass('active');
                }
            }
            if (currentTool instanceof SelectTool) {
                if (context.selectionFigures.length === 0) {
                    context.selectFigure(_this);
                } else {
                    let contain = false;
                    for (let figure of context.selectionFigures) {
                        if (figure === _this) {
                            contain = true;
                            break;
                        }
                    }
                    if (!contain) {
                        context.resetSelection();
                        context.selectFigure(_this);
                    }
                }
            }

            if (!(currentTool instanceof ConnectionTool)) {
                return;
            }
            var x = e.offsetX;
            var y = e.offsetY;
            var connection = context.currentConnection;
            if (connection) {
                if (_this.in === 0) {
                    MsgBox.alert(`当前节点不允许有进入的连线.<br>Current node no incoming connections are allowed`);
                    return;
                }
                if (_this.in !== -1 && _this.toConnections.length >= _this.in) {
                    MsgBox.alert(`当前节点进入的连线最多只能有${_this.in}条<br>Current node max incoming connections is ${_this.in}`);
                    return;
                }
                connection.endX = x;
                connection.endY = y;
                if (connection.from !== _this) {
                    connection.endPath(_this);
                    context.currentConnection = null;
                    const fromUUID = connection.from.uuid, toUUID = _this.uuid, uuid = connection.uuid;
                    context.addRedoUndo({
                        redo: function () {
                            const from = context.getNodeByUUID(fromUUID), to = context.getNodeByUUID(toUUID);
                            connection = new Connection(from, { endX: from.rect.attr('x'), endY: from.rect.attr('y') });
                            connection.uuid = uuid;
                            connection.endPath(to);
                        },
                        undo: function () {
                            _this.context.removeFigureByUUID(uuid);
                        }
                    })
                } else {
                    MsgBox.alert('连线的起始节点不能为同一节点<br>Connection start/end must be different.');
                }
            } else {
                if (_this.out === 0) {
                    MsgBox.alert('当前节点不允许有出去的连线<br>Current node has no outcoming connection');
                    return;
                }
                if (_this.out !== -1 && _this.fromConnections.length >= _this.out) {
                    MsgBox.alert(`当前节点出去的连线最多只能有${_this.out}条<br>Current node max outcoming connections is ${_this.out}`);
                    return;
                }
                connection = new Connection(_this, { endX: x, endY: y });
                context.currentConnection = connection;
            }
        };
        this.rect.mouseover(mouseOver);
        this.rect.mousedown(mouseDown);
        this.icon.mouseover(mouseOver);
        this.icon.mousedown(mouseDown);

        var dragStart = function () {
            var rect = _this.rect;
            const currentTool = context.currentTool;
            if (!currentTool || !(currentTool instanceof SelectTool)) {
                return;
            }
            let contain = false;
            const selectionFigures = context.selectionFigures;
            for (let figure of selectionFigures) {
                if (figure === _this) {
                    contain = true;
                    break;
                }
            }
            if (!contain) {
                context.resetSelection();
                return;
            }
            selectionFigures.forEach((figure, index) => {
                if (!(figure instanceof Connection)) {
                    figure._recordRectPosition();
                }
            });
            rect.ow = rect.attr('width');
            rect.oh = rect.attr('height');
            rect.dragging = true;
        };

        var dragMove = function (dx, dy) {
            const currentTool = context.currentTool;
            if (!currentTool || !(currentTool instanceof SelectTool)) {
                return;
            }
            if (_this.context.snapto) {
                dx -= dx % 10, dy -= dy % 10;
            }
            const selectionFigures = context.selectionFigures;
            const rect = _this.rect, icon = _this.icon;
            let x = rect.ox + dx, y = rect.oy + dy;
            if (x < 1 || y < 1) {
                return;
            }
            _this.context.resizePaper(x + 150, y + 150);
            let width, height;
            switch (this.attr('cursor')) {
                case 'nw-resize':
                    width = rect.ow - dx, height = rect.oh - dy;
                    if (width > 20 && height > 20) {
                        rect.attr({
                            x,
                            y,
                            width,
                            height
                        });
                    }
                    break;
                case 'ne-resize':
                    width = rect.ow + dx, height = rect.oh - dy;
                    if (width > 20 && height > 20) {
                        rect.attr({
                            y,
                            width,
                            height
                        });
                    }

                    break;
                case 'se-resize':
                    width = rect.ow + dx, height = rect.oh + dy;
                    if (width > 20 && height > 20) {
                        rect.attr({
                            width,
                            height
                        });
                    }
                    break;
                case 'sw-resize':
                    width = rect.ow - dx, height = rect.oh + dy;
                    if (width > 20 && height > 20) {
                        rect.attr({
                            x,
                            width,
                            height
                        });
                    }
                    break;
                default:
                    selectionFigures.forEach((figure, index) => {
                        if (!(figure instanceof Connection)) {
                            figure._moveRect(dx, dy)
                        }
                    });
                    break;
            }
            selectionFigures.forEach((figure, index) => {
                if (!(figure instanceof Connection)) {
                    figure._moveAndResizeTextAndIcon();
                    figure._resetConnections();
                }
            });
            // 节点拖动时，添加对齐的协助线
            if (selectionFigures.length == 1 && !(selectionFigures[0] instanceof Connection)) {
                const selectFigure = selectionFigures[0];
                const clientHeight = context.container[0].clientHeight;
                const clientX = selectFigure.rect.attrs.x + parseInt(selectFigure.rect.attrs.width / 2);
                const clientY = selectFigure.rect.attrs.y + parseInt(selectFigure.rect.attrs.height / 2);
                let horizontalLine = context.flowDesigner.horizontalLine;
                let verticalLine = context.flowDesigner.verticalLine;
                horizontalLine.css("top", event.clientY);
                verticalLine.css("height", clientHeight);
                verticalLine.css("left", event.clientX);
                verticalLine.css("top", "80px");

                const nodeName = selectFigure.name;
                for (let figure of context.allFigures) {
                    if (figure instanceof Connection) {
                        continue;
                    }
                    if (figure.name != nodeName) {
                        let matchx = false, matchy = false;
                        const nodeX = figure.rect.attrs.x + parseInt(figure.rect.attrs.width / 2);
                        const nodeY = figure.rect.attrs.y + parseInt(figure.rect.attrs.height / 2);
                        if (Math.abs(nodeX - clientX) < 5) {
                            verticalLine.css("display", "block");
                            matchx = true;
                        } else {
                            verticalLine.css("display", "none");
                        }
                        if (Math.abs(nodeY - clientY) < 5) {
                            horizontalLine.css("display", "block");
                            matchy = true;
                        } else {
                            horizontalLine.css("display", "none");
                        }
                        if (matchy || matchx) {
                            break;
                        }
                    }
                }
            }
        };
        var dragEnd = function () {
            _this.rect.dragging = false;
            const selectionUUIDs = [], xyMap = new Map(), oldXYMap = new Map(), ow = _this.rect.ow, oh = _this.rect.oh, w = _this.rect.attr('width'), h = _this.rect.attr('height'), nodeUUID = _this.uuid;
            for (const figure of context.selectionFigures) {
                if (!(figure instanceof Connection)) {
                    selectionUUIDs.push(figure.uuid);
                    let ox = figure.rect.ox, oy = figure.rect.oy;
                    ox = ox ? ox : 0, oy = oy ? oy : 0;
                    const x = figure.rect.attr('x'), y = figure.rect.attr('y');
                    xyMap.set(figure.uuid, { x, y });
                    oldXYMap.set(figure.uuid, { x: ox, y: oy });
                }
            }
            _this.context.addRedoUndo({
                redo: function () {
                    for (const uuid of selectionUUIDs) {
                        const node = context.getNodeByUUID(uuid);
                        const pos = xyMap.get(uuid);
                        node.rect.attr({ x: pos.x, y: pos.y });
                        if (uuid === nodeUUID) {
                            node.rect.attr({ width: w, height: h });
                        }
                    }
                    for (const uuid of selectionUUIDs) {
                        const node = context.getNodeByUUID(uuid);
                        node._moveAndResizeTextAndIcon();
                        node._resetConnections();
                    }
                },
                undo: function () {
                    for (const uuid of selectionUUIDs) {
                        const node = context.getNodeByUUID(uuid);
                        const pos = oldXYMap.get(uuid);
                        node.rect.attr({ x: pos.x, y: pos.y });
                        if (uuid === nodeUUID) {
                            node.rect.attr({ width: ow, height: oh });
                        }
                    }
                    for (const uuid of selectionUUIDs) {
                        const node = context.getNodeByUUID(uuid);
                        node._moveAndResizeTextAndIcon();
                        node._resetConnections();
                    }
                }
            });
            if (window._setDirty) {
                window._setDirty();
            }
            if (context.selectionFigures.length == 1 && !(context.selectionFigures[0] instanceof Connection)) {
                let horizontalLine = context.flowDesigner.horizontalLine;
                let verticalLine = context.flowDesigner.verticalLine;
                horizontalLine.css("display", "none");
                verticalLine.css("display", "none");
            }
        };
        this.rect.drag(dragMove, dragStart, dragEnd);
        this.icon.drag(dragMove, dragStart, dragEnd);
    }

    remove() {
        const toConnections = [...this.toConnections], fromConnections = [...this.fromConnections];
        toConnections.forEach((conn, index) => {
            this.context.removeFigureByUUID(conn.uuid);
        });
        fromConnections.forEach((conn, index) => {
            this.context.removeFigureByUUID(conn.uuid);
        });
        this.text.remove();
        this.icon.remove();
        this.rect.remove();
        if (window._setDirty) {
            window._setDirty();
        }
    }

    _recordRectPosition() {
        this.rect.ox = this.rect.attr('x');
        this.rect.oy = this.rect.attr('y');
    }

    _moveRect(dx, dy) {
        let x, y;
        if (this.rect.ox && this.rect.ox !== 0) {
            x = this.rect.ox + dx, y = this.rect.oy + dy;
        } else {
            x = this.rect.attr('x') + dx, y = this.rect.attr('x') + dy;
        }
        this.rect.attr({ x, y });
    }

    _moveAndResizeTextAndIcon() {
        var rectWidth = this.rect.attr('width'), rectHeight = this.rect.attr('height');
        this.text.attr({ x: this.rect.attr('x') + rectWidth / 2, y: this.rect.attr('y') + rectHeight - 16 });
        this.icon.attr({ x: this.rect.attr('x'), y: this.rect.attr('y'), width: rectWidth, height: rectHeight - 30 });
    }

    _resetConnections() {
        const conns = [...this.fromConnections, ...this.toConnections];
        conns.forEach((conn, index) => {
            conn.updatePath();
        });
    }

    getPathInfo(forIntersection) {
        let x = this.rect.attr("x"), y = this.rect.attr("y"), w = this.rect.attr("width"), h = this.rect.attr("height");
        if (forIntersection) {
            x -= 5, y -= 5, w += 10, h += 10;
        }
        return "M " + x + " " + y + " L " + (x + w) + " " + y + " L " + (x + w) + " " + (y + h) + " L " + x + "  " + (y + h) + " L " + x + "  " + y;
    }
}